<?php
ob_implicit_flush();
error_reporting(E_ALL & ~E_NOTICE);
//游戏逻辑控制
include 'game.php';
//配置文件 网址 端口
include 'config.php';

$ws = new websocket($config['address'], $config['port']);

class websocket {
    var $master;      //连接 server 的 client
    var $sockets   = array(); // 不同状态的 socket 管理
    var $handshake = array(); // 判断是否握手
    var $game;       //游戏的对局数据
    var $change;     //有变化的连接 (新增的socket)

    function __construct($address, $port) {
        //建立一个 socket 套接字
        $this->master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or die("socket_create() failed");
        socket_set_option($this->master, SOL_SOCKET, SO_REUSEADDR, 1) or die("socket_option() failed");
        socket_bind($this->master, $address, $port) or die("socket_bind() failed");
        //一次监听x个不同的连接
        socket_listen($this->master) or die("socket_listen() failed");


        //$this->sockets[] = $this->master;
        $this->sockets = array($this->master);
        $this->changes = $this->sockets;

        $this->game = new game($this);

        // start log
        echo("Master socket  : " . $this->master . "\n");

        while(true) {
            $this->changes = $this->sockets;
            //自动选择来消息的 socket 如果是握手 自动选择主机
            $write  = null;
            $except = null;
            socket_select($this->changes, $write, $except, null);

            //echo '=============change========', "\n";
            foreach($this->changes as $socket) {
                //print_r($this->sockets);
                //连接主机的 client
                if($socket == $this->master) {
                    $client = socket_accept($this->master);
                    if($client < 0) { //无效的连接
                        continue;
                    } else {          //新的连接 放进socket池
                        $this->sockets[] = $client;
                        $key = uniqid();
                        $this->handshake[$key]=array(
                            'socket' => $client,  //记录新连接进来client的socket信息
                            'shou'   => false       //标志该socket资源没有完成握手
                        );
                    }
                } else {
                    $id = $this->search($socket);
                    $bytes = socket_recv($socket, $buffer, 2048, 0);
                    if($bytes < 7) {//长度小于7  表示断开连接了
                        $buffer = '{"type":"leaveRoom"}';
                        $this->game->event($buffer, $id, $socket);
                        continue;
                    }
                    if(!isset($this->handshake[$id]) || !$this->handshake[$id]['shou']) {
                        // 如果没有握手，先握手回应
                        $this->doHandShake($socket, $buffer, $id);
                    } else {
                        // 如果已经握手，直接接受数据，并处理
                        $buffer = $this->decode($buffer);
                        //process($socket, $buffer);
                        $this->game->event($buffer, $id, $socket);
                        continue;
                    }
                }
            }
        }
    }

    function search($socket) {
        foreach ($this->handshake as $k=>$v){
            if($socket == $v['socket'])
                return $k;
        }
        return false;
    }

    // 返回数据
    function send($msg, $client) {
        $msg = $this->frame($msg);
        socket_write($client, $msg, strlen($msg));
        return true;
    }

    function frame($msg) {
        $msg = preg_replace(array(
            '/\r$/',
            '/\n$/',
            '/\r\n$/',
        ), '', $msg);
        $frame    = array();
        $frame[0] = '81';
        $len      = strlen($msg);
        $frame[1] = $len < 16 ? '0' . dechex($len) : dechex($len);
        $frame[2] = $this->ord_hex($msg);
        $data     = implode('', $frame);

        return pack("H*", $data);
    }

    function ord_hex($data) {
        $msg = '';
        $l   = strlen($data);
        for($i = 0; $i < $l; $i++) {
            $msg .= dechex(ord($data{$i}));
        }
        return $msg;
    }


    function dohandshake($socket, $req, $id) {
        // 获取加密key
        $acceptKey = $this->encry($req);
        $upgrade   = "HTTP/1.1 101 Switching Protocols\r\n" . "Upgrade: websocket\r\n" . "Connection: Upgrade\r\n" . "Sec-WebSocket-Accept: " . $acceptKey . "\r\n" . "\r\n";
        // 写入socket
        socket_write($socket, $upgrade, strlen($upgrade));
        // 标记握手已经成功，下次接受数据采用数据帧格式
        $this->handshake[$id]['shou'] = true;
    }

    function encry($req) {
        $key  = $this->getKey($req);
        $mask = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";

        return base64_encode(sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
    }

    function getKey($req) {
        $key = null;
        if(preg_match("/Sec-WebSocket-Key: (.*)\r\n/", $req, $match)) {
            $key = $match[1];
        }
        return $key;
    }

    // 解析数据帧
    function decode($buffer) {
        $decoded = null;
        $len = ord($buffer[1]) & 127;

        if($len === 126) {
            $masks = substr($buffer, 4, 4);
            $data  = substr($buffer, 8);
        } else if($len === 127) {
            $masks = substr($buffer, 10, 4);
            $data  = substr($buffer, 14);
        } else {
            $masks = substr($buffer, 2, 4);
            $data  = substr($buffer, 6);
        }
        for($index = 0; $index < strlen($data); $index++) {
            $decoded .= $data[$index] ^ $masks[$index % 4];
        }

        return $decoded;
    }

    function close ($id) {
        //断开相应socket
        socket_close($this->handshake[$id]['socket']);
        //重新定义sockets连接池
        $this->sockets = array($this->master);

        unset($this->handshake[$id]);
    }
}